/*
 * Copyright 2008, 2009 by Brian Dominy <brian@oddchange.com>
 *
 * This file is part of FreeWPC.
 *
 * FreeWPC is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * FreeWPC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FreeWPC; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/*
 * Generic spinner driver
 *
 * Spinner handling requires polling the switch faster than software
 * can normally dispatch transitions.  The strategy is to monitor
 * the switch here for a little while after the first transition,
 * and keep throwing "spin events" until a timeout occurs.  While
 * this is happening, switch scanning should not look at the switch.
 *
 * Unfortunately, process level can only sleep in 16ms units.
 *
 * Note that it is possible for a spinner to keep flipping even after
 * the last ball has drained.
 */

@@class spinner
@@parameter sw_number
@@parameter sw_event

@@
@@file @self.h
@@

#include <freewpc.h>

/** The number of times the fast handler has seen the spinner
toggle.  This is 2X the number of activations. */
extern U8 @self_counter;


/** The previous reading of the spinner switch at IRQ time */
extern U8 @self_prev_state;


/* Fast handler for a spinner switch.
 * This function runs at IRQ time and is only responsible
 * for counting the number of transitions that occur, as this
 * can happen _very_ fast.  The slow handler will then
 * process all of the spins in one dispatch.  This cuts down
 * the overhead in switch scanning and task creation.
 */
extern inline void @self_service (void)
{
	U8 state = rt_switch_poll (@sw_number);
	if (state != @self_prev_state)
	{
		@self_counter++;
	}
	@self_prev_state = state;
}


@@
@@file @self.c
@@

#include <freewpc.h>

U8 @self_prev_state;
U8 @self_counter;
U8 @self_timer;


/* Slow handler for a spinner switch. */
CALLSET_ENTRY (@self, @sw_event)
{
	/* Don't do anything if another instance of the switch
	handler is running. */
	if (!task_find_gid (GID_@self_MONITOR))
	{
		task_setgid (GID_@self_MONITOR);

		/* Notify handlers that the spinner was just started */
		callset_invoke (@sw_event_start);

		/* Throw as many spins as the fast handler has counted.
		Note that the counter actually reflects transitions, which
		is 2x the total number of spins.  So only process every
		other transition.
		When the counter is zero, decrement the timer that says
		how long we continue to poll before getting out.
		Note that counter=0 should never happen, but it is detected
		anyway. */

work_to_do:
		while (@self_counter)
		{
			if (@self_counter & 1)
			{
				callset_invoke (@sw_event_slow);
				task_sleep (TIME_16MS);
			}
			/* It is important that the following generate only
			one instruction, so that no locking is required!
			TODO - use an explicit atomic primitive */
			@self_counter--;
		}

		/* Monitor the counter for some time after it goes to
		 * zero, in case it flips again, to try to avoid the
		 * overhead of redispatching.  This is not technically
		 * necessary, just an optimization. */
		@self_timer = TIME_400MS;
		while (@self_timer)
		{
			task_sleep (TIME_33MS);
			@self_timer -= TIME_33MS;
			if (@self_counter)
				goto work_to_do;
		}
	}
}

CALLSET_ENTRY (@self, init)
{
	@self_counter = 0;
	@self_prev_state = 0;
}

/* vim: set filetype=c: */
